Implement cargo-new
authorAlex Crichton <alex@alexcrichton.com>
Tue, 22 Jul 2014 05:19:31 +0000 (22:19 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Tue, 22 Jul 2014 17:36:19 +0000 (10:36 -0700)
This command is used to create a new cargo repository at a destination that
previously does not exist. A separate command, cargo-init, will be implemented
to initialize an already-existing repository.

cc #21

Makefile
src/bin/cargo-new.rs [new file with mode: 0644]
src/cargo/ops/cargo_new.rs [new file with mode: 0644]
src/cargo/ops/mod.rs
src/cargo/util/config.rs
tests/test_cargo_compile_git_deps.rs
tests/test_cargo_new.rs [new file with mode: 0644]
tests/tests.rs

index 13d5d6f3b33a12c9160c12363204823f87f2092f..548b61d85babf4e36626f1c30ec399300e000424 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -34,7 +34,8 @@ BINS = cargo \
             cargo-git-checkout \
                 cargo-test \
                 cargo-run \
-                cargo-version
+                cargo-version \
+                cargo-new
 
 SRC = $(shell find src -name '*.rs' -not -path 'src/bin*')
 
diff --git a/src/bin/cargo-new.rs b/src/bin/cargo-new.rs
new file mode 100644 (file)
index 0000000..efafc54
--- /dev/null
@@ -0,0 +1,52 @@
+#![feature(phase)]
+
+extern crate cargo;
+
+#[phase(plugin, link)]
+extern crate hammer;
+
+#[phase(plugin, link)]
+extern crate log;
+
+extern crate serialize;
+
+use std::os;
+use cargo::ops;
+use cargo::core::MultiShell;
+use cargo::util::{CliResult, CliError};
+
+#[deriving(PartialEq,Clone,Decodable,Encodable)]
+pub struct Options {
+    git: bool,
+    bin: bool,
+    rest: Vec<String>,
+}
+
+hammer_config!(Options "Create a new cargo project")
+
+fn main() {
+    cargo::execute_main_without_stdin(execute);
+}
+
+fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>> {
+    debug!("executing; cmd=cargo-new; args={}", os::args());
+
+    let Options { git, mut rest, bin } = options;
+
+    let path = match rest.remove(0) {
+        Some(path) => path,
+        None => return Err(CliError::new("must have a path as an argument", 1))
+    };
+
+    let opts = ops::NewOptions {
+        git: git,
+        path: path.as_slice(),
+        bin: bin,
+    };
+
+    ops::new(opts, shell).map(|_| None).map_err(|err| {
+        CliError::from_boxed(err, 101)
+    })
+}
+
+
diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs
new file mode 100644 (file)
index 0000000..3039a5e
--- /dev/null
@@ -0,0 +1,94 @@
+use std::os;
+use std::io;
+use std::io::{fs, File, Command};
+
+use ops;
+use util::{CargoResult, human, ProcessError, Config, ChainError, process};
+use core::shell::MultiShell;
+use core::source::Source;
+use sources::PathSource;
+
+macro_rules! git( ($($a:expr),*) => ({
+    process("git") $(.arg($a))* .exec_with_output()
+}) )
+
+pub struct NewOptions<'a> {
+    pub git: bool,
+    pub bin: bool,
+    pub path: &'a str,
+}
+
+pub fn new(opts: NewOptions, shell: &mut MultiShell) -> CargoResult<()> {
+    let config = try!(Config::new(shell, false, None, None));
+    let path = os::getcwd().join(opts.path);
+    if path.exists() {
+        return Err(human(format!("Destination `{}` already exists",
+                                 path.display())))
+    }
+    let name = path.filename_str().unwrap();
+    mk(&path, name, &opts).chain_error(|| {
+        human(format!("Failed to create project `{}` at `{}`",
+                      name, path.display()))
+    })
+}
+
+fn mk(path: &Path, name: &str, opts: &NewOptions) -> CargoResult<()> {
+
+    if opts.git {
+        try!(git!("init", path));
+        try!(File::create(&path.join(".gitignore")).write(b"/target\n"));
+    } else {
+        try!(fs::mkdir(path, io::UserRWX));
+    }
+
+    let author = try!(discover_author());
+    try!(File::create(&path.join("Cargo.toml")).write_str(format!(
+r#"[package]
+
+name = "{}"
+version = "0.0.1"
+authors = ["{}"]
+"#, name, author).as_slice()));
+
+    try!(fs::mkdir(&path.join("src"), io::UserRWX));
+
+    if opts.bin {
+        try!(File::create(&path.join("src/main.rs")).write_str("\
+fn main() {
+    println!(\"Hello, world!\")
+}
+"));
+    } else {
+        try!(File::create(&path.join("src/lib.rs")).write_str("\
+#[test]
+fn it_works() {
+}
+"));
+    }
+
+    Ok(())
+}
+
+fn discover_author() -> CargoResult<String> {
+    let name = match git!("config", "user.name") {
+        Ok(out) => String::from_utf8_lossy(out.output.as_slice()).into_string(),
+        Err(..) => match os::getenv("USER") {
+            Some(user) => user,
+            None => return Err(human("could not determine the current user, \
+                                      please set $USER"))
+        }
+    };
+
+    let email = match git!("config", "user.email") {
+        Ok(out) => Some(String::from_utf8_lossy(out.output.as_slice()).into_string()),
+        Err(..) => None,
+    };
+
+    let name = name.as_slice().trim().to_string();
+    let email = email.map(|s| s.as_slice().trim().to_string());
+
+    Ok(match (name, email) {
+        (name, Some(email)) => format!("{} <{}>", name, email),
+        (name, None) => name,
+    })
+}
index cba0e894ad867ed0f984cdf6198b9fb19681ad34..5c16029fe2b92f5f0d9a99e3cdf125b30d5efecc 100644 (file)
@@ -3,9 +3,11 @@ pub use self::cargo_compile::{compile, CompileOptions};
 pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
 pub use self::cargo_rustc::compile_targets;
 pub use self::cargo_run::run;
+pub use self::cargo_new::{new, NewOptions};
 
 mod cargo_clean;
 mod cargo_compile;
 mod cargo_read_manifest;
 mod cargo_rustc;
 mod cargo_run;
+mod cargo_new;
index ef139607f7413c4e10d68e10f041d5a9fecda072..9b43be817d379e4b88288aacca579a40aa6d41db 100644 (file)
@@ -39,6 +39,8 @@ impl<'a> Config<'a> {
         })
     }
 
+    pub fn home(&self) -> &Path { &self.home_path }
+
     pub fn git_db_path(&self) -> Path {
         self.home_path.join(".cargo").join("git").join("db")
     }
index 9af4b50042007799f30798fa8e0bc06ff98e1627..3a0f52b2c7c9ee8b06bdb6e5df31354acf8491f8 100644 (file)
@@ -1,4 +1,4 @@
-use std::io::{File, TempDir};
+use std::io::File;
 
 use support::{ProjectBuilder, ResultTest, project, execs, main_file, paths};
 use support::{cargo_dir};
diff --git a/tests/test_cargo_new.rs b/tests/test_cargo_new.rs
new file mode 100644 (file)
index 0000000..0b1e87d
--- /dev/null
@@ -0,0 +1,103 @@
+use std::io::{fs, UserRWX, File};
+use std::os;
+
+use support::{execs, paths, cargo_dir, ResultTest};
+use hamcrest::{assert_that, existing_file, existing_dir};
+
+use cargo::util::{process, ProcessBuilder};
+
+fn setup() {
+}
+
+fn my_process(s: &str) -> ProcessBuilder {
+    process(s)
+        .cwd(paths::root())
+        .env("HOME", Some(paths::home()))
+}
+
+fn cargo_process(s: &str) -> ProcessBuilder {
+    process(cargo_dir().join(s))
+        .cwd(paths::root())
+        .env("HOME", Some(paths::home()))
+}
+
+test!(simple_lib {
+    os::setenv("USER", "foo");
+    assert_that(cargo_process("cargo-new").arg("foo"),
+                execs().with_status(0));
+
+    assert_that(&paths::root().join("foo"), existing_dir());
+    assert_that(&paths::root().join("foo/Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("foo/src/lib.rs"), existing_file());
+
+    assert_that(cargo_process("cargo-build").cwd(paths::root().join("foo")),
+                execs().with_status(0));
+})
+
+test!(simple_bin {
+    os::setenv("USER", "foo");
+    assert_that(cargo_process("cargo-new").arg("foo").arg("--bin"),
+                execs().with_status(0));
+
+    assert_that(&paths::root().join("foo"), existing_dir());
+    assert_that(&paths::root().join("foo/Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("foo/src/main.rs"), existing_file());
+
+    assert_that(cargo_process("cargo-build").cwd(paths::root().join("foo")),
+                execs().with_status(0));
+    assert_that(&paths::root().join(format!("foo/target/foo{}",
+                                            os::consts::EXE_SUFFIX)),
+                existing_file());
+})
+
+test!(simple_git {
+    os::setenv("USER", "foo");
+    assert_that(cargo_process("cargo-new").arg("foo").arg("--git"),
+                execs().with_status(0));
+
+    assert_that(&paths::root().join("foo"), existing_dir());
+    assert_that(&paths::root().join("foo/Cargo.toml"), existing_file());
+    assert_that(&paths::root().join("foo/src/lib.rs"), existing_file());
+    assert_that(&paths::root().join("foo/.git"), existing_dir());
+    assert_that(&paths::root().join("foo/.gitignore"), existing_file());
+
+    assert_that(cargo_process("cargo-build").cwd(paths::root().join("foo")),
+                execs().with_status(0));
+})
+
+test!(no_argument {
+    assert_that(cargo_process("cargo-new"),
+                execs().with_status(1)
+                       .with_stderr("must have a path as an argument\n"));
+})
+
+test!(existing {
+    let dst = paths::root().join("foo");
+    fs::mkdir(&dst, UserRWX).assert();
+    assert_that(cargo_process("cargo-new").arg("foo"),
+                execs().with_status(101)
+                       .with_stderr(format!("Destination `{}` already exists\n",
+                                            dst.display())));
+})
+
+test!(finds_author_user {
+    assert_that(cargo_process("cargo-new").arg("foo").env("USER", Some("foo")),
+                execs().with_status(0));
+
+    let toml = paths::root().join("foo/Cargo.toml");
+    let toml = File::open(&toml).read_to_string().assert();
+    assert!(toml.as_slice().contains(r#"authors = ["foo"]"#));
+})
+
+test!(finds_author_git {
+    my_process("git").args(["config", "--global", "user.name", "bar"])
+                     .exec().assert();
+    my_process("git").args(["config", "--global", "user.email", "baz"])
+                     .exec().assert();
+    assert_that(cargo_process("cargo-new").arg("foo").env("USER", Some("foo")),
+                execs().with_status(0));
+
+    let toml = paths::root().join("foo/Cargo.toml");
+    let toml = File::open(&toml).read_to_string().assert();
+    assert!(toml.as_slice().contains(r#"authors = ["bar <baz>"]"#));
+})
index 0b1852708dd39149148d8a9ee4e9133354812720..80ca230b8eee77f0931633b633c3bb117e9fd535 100644 (file)
@@ -29,3 +29,4 @@ mod test_shell;
 mod test_cargo_cross_compile;
 mod test_cargo_run;
 mod test_cargo_version;
+mod test_cargo_new;